Mini Project 3: Visualizing and Maintaining the Green Canopy of NYC
Author
Wendy Fung-Wu
Introduction
New York City’s street trees are a critical component of its urban infrastructure.They provide shade, reduce urban heat, improve air quality, absorb stormwater, and contribute to the aesthetic and social quality of dense neighborhoods. However, the benefits of this “green canopy” are not distributed evenly across the city, and many trees are aging, damaged, or dead.Identifying where trees are located—and how their condition varies across neighborhoods—is an essential step toward making informed and equitable investment decisions.
Data Acquisition
Task 1: Download NYC City Council District Boundaries
Show code
# You’ll need sf (and optionally tidyverse for later work)if (!requireNamespace("sf", quietly =TRUE)) install.packages("sf")library(sf)download_nyc_council_districts <-function(dir_path ="data/mp03",zip_url ="https://www1.nyc.gov/assets/planning/download/zip/data-maps/open-data/nycc_20b.zip",simplify_tolerance =NULL# in meters; e.g., 5 or 10 if you want to simplify) {# 1. Ensure data/mp03 existsif (!dir.exists(dir_path)) {dir.create(dir_path, recursive =TRUE) }# 2. Set local file paths zip_file <-file.path(dir_path, "nyc_council_districts.zip") shp_dir <-file.path(dir_path, "nyc_council_districts")# 3. Download the zip ONLY if neededif (!file.exists(zip_file)) {message("Downloading NYC City Council District shapefile zip...")download.file(zip_url, destfile = zip_file, mode ="wb") } else {message("Using cached zip file: ", zip_file) }# 4. Unzip ONLY if neededif (!dir.exists(shp_dir)) {message("Unzipping shapefile contents...")unzip(zip_file, exdir = shp_dir) } else {message("Using existing unzipped directory: ", shp_dir) }# 5. Find the .shp file inside the unzipped directory shp_path <-list.files( shp_dir,pattern ="\\.shp$",full.names =TRUE,recursive =TRUE )if (length(shp_path) ==0L) {stop("No .shp file found in ", shp_dir) }# If there are multiple, just take the first one (typically nycc.shp) shp_path <- shp_path[1]message("Reading shapefile: ", shp_path) nyc_cc <- sf::st_read(shp_path, quiet =TRUE)# 6. Transform to WGS 84 (as required) nyc_cc <- sf::st_transform(nyc_cc, crs ="WGS84")# 7. OPTIONAL: simplify the geometry if a tolerance is suppliedif (!is.null(simplify_tolerance)) {message("Simplifying geometry with dTolerance = ", simplify_tolerance, " meters...") nyc_cc$geometry <- sf::st_simplify( nyc_cc$geometry,dTolerance = simplify_tolerance ) }# 8. Return the transformed (and possibly simplified) sf object nyc_cc}council_districts <-download_nyc_council_districts(simplify_tolerance =NULL)
library(ggplot2)library(sf)library(dplyr)trees_plot <- trees_sfggplot() +# Council district polygonsgeom_sf(data = council_districts,fill =NA,color ="grey60",linewidth =0.3) +# Tree pointsgeom_sf(data = trees_plot,color ="darkgreen",alpha =0.25,size =0.1) +coord_sf() +labs(title ="NYC Street Trees Overlaid on City Council Districts",subtitle ="Forestry Tree Points (NYC Open Data) and Council District Boundaries",caption ="Data: NYC Dept. of City Planning & NYC Open Data (Forestry Tree Points)") +theme_minimal()
Task 4: District-Level Analysis of Tree Coverage
Q1: Which council district has the most trees?
Show code
library(sf)library(dplyr)library(knitr)library(kableExtra)library(scales)# Build trees_districts here if it doesn't already exist ---------------------if (!exists("trees_districts")) {trees_districts <-st_join(trees_sf,council_districts |>dplyr::select(CounDist, Shape_Area),join = st_within,left =TRUE) |>mutate(borough =case_when(CounDist >=1& CounDist <=10~"Manhattan",CounDist >=11& CounDist <=18~"Bronx",CounDist >=19& CounDist <=32~"Queens",CounDist >=33& CounDist <=48~"Brooklyn",CounDist >=49& CounDist <=51~"Staten Island",TRUE~NA_character_))}# Q1: Which council district has the most trees? -----------------------------trees_by_district <- trees_districts |>st_drop_geometry() |>group_by(CounDist) |>summarize(n_trees =n(), .groups ="drop") |>arrange(desc(n_trees))# Save the top district for the written answerq1_top <- trees_by_district |>slice_head(n =1)# Pretty table: top 10 districts by number of trees --------------------------trees_by_district |>slice_head(n =10) |>rename(`Council District`= CounDist,`Number of Trees`= n_trees) |>mutate(`Number of Trees`=comma(`Number of Trees`)) |>kable(format ="html",align =c("c", "r"),caption ="Top 10 NYC Council Districts by Number of Street Trees") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Top 10 NYC Council Districts by Number of Street Trees
Council District
Number of Trees
51
51,236
19
34,389
50
33,035
23
30,712
31
23,152
49
21,047
27
20,115
32
19,508
24
18,993
30
18,551
Answer (Q1): Council District 51 has the most trees, with 51,236 trees.
Q2: Which council district has the highest density of trees?
Show code
library(sf)library(dplyr)library(knitr)library(kableExtra)library(scales)# Ensure trees_districts exists (same logic as Q1) ---------------------------if (!exists("trees_districts")) {trees_districts <-st_join(trees_sf,council_districts |>dplyr::select(CounDist, Shape_Area),join = st_within,left =TRUE) |>mutate(borough =case_when(CounDist >=1& CounDist <=10~"Manhattan",CounDist >=11& CounDist <=18~"Bronx",CounDist >=19& CounDist <=32~"Queens",CounDist >=33& CounDist <=48~"Brooklyn",CounDist >=49& CounDist <=51~"Staten Island",TRUE~NA_character_))}# Trees per district (from Q1) -----------------------------------------------trees_by_district <- trees_districts |>st_drop_geometry() |>group_by(CounDist) |>summarize(n_trees =n(), .groups ="drop")# Bring in Shape_Area from council_districts ---------------------------------district_area <- council_districts |>st_drop_geometry() |>select(CounDist, Shape_Area)# Compute density: trees per km^2 --------------------------------------------density_by_district <- trees_by_district |>left_join(district_area, by ="CounDist") |>mutate(area_km2 = Shape_Area /1e6, # assuming Shape_Area is in m^2trees_per_km2 = n_trees / area_km2) |>arrange(desc(trees_per_km2))# Top district for inline answerq2_top <- density_by_district |>slice_head(n =1)# Pretty table: Top 10 by tree density, including Shape_Area -----------------density_by_district |>slice_head(n =10) |>rename(`Council District`= CounDist,`Number of Trees`= n_trees,`Shape Area (m²)`= Shape_Area,`Area (km²)`= area_km2,`Trees per km²`= trees_per_km2) |>mutate(`Number of Trees`=comma(`Number of Trees`),`Shape Area (m²)`=comma(`Shape Area (m²)`),`Area (km²)`=round(`Area (km²)`, 2),`Trees per km²`=round(`Trees per km²`, 1)) |>kable(format ="html",align =c("c", "r", "r", "r", "r"),caption ="Top 10 NYC Council Districts by Tree Density") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Top 10 NYC Council Districts by Tree Density
Council District
Number of Trees
Shape Area (m²)
Area (km²)
Trees per km²
9
8,213
57,262,987
57.26
143.4
5
4,982
37,216,445
37.22
133.9
35
10,525
81,510,739
81.51
129.1
7
6,572
51,409,605
51.41
127.8
44
11,659
92,757,492
92.76
125.7
25
7,851
63,560,956
63.56
123.5
4
8,522
70,494,165
70.49
120.9
14
6,240
51,648,156
51.65
120.8
39
13,860
116,496,928
116.50
119.0
36
9,023
76,221,712
76.22
118.4
Answer (Q2): Council District 9 has the highest tree density,
with about 143.4 trees per square kilometer.
Q3: Which district has the highest fraction of dead trees?
Show code
library(sf)library(dplyr)library(knitr)library(kableExtra)library(scales)# Ensure trees_districts exists (same pattern as Q1 & Q2) --------------------if (!exists("trees_districts")) {trees_districts <-st_join(trees_sf,council_districts |>dplyr::select(CounDist, Shape_Area),join = st_within,left =TRUE) |>mutate(borough =case_when(CounDist >=1& CounDist <=10~"Manhattan",CounDist >=11& CounDist <=18~"Bronx",CounDist >=19& CounDist <=32~"Queens",CounDist >=33& CounDist <=48~"Brooklyn",CounDist >=49& CounDist <=51~"Staten Island",TRUE~NA_character_))}# Compute fraction of dead trees per district --------------------------------# For the NYC Street Tree Census (uvpi-gqnh), `status == "Dead"` marks dead trees.dead_fraction <- trees_districts |>st_drop_geometry() |>filter(!is.na(CounDist)) |>mutate(is_dead = status =="Dead") |>group_by(CounDist) |>summarize(n_trees =n(),n_dead =sum(is_dead, na.rm =TRUE),frac_dead = n_dead / n_trees,.groups ="drop") |>arrange(desc(frac_dead))# Top district for inline answerq3_top <- dead_fraction |>slice_head(n =1)# Pretty table: Top 10 districts by fraction of dead trees -------------------dead_fraction |>slice_head(n =10) |>rename(`Council District`= CounDist,`Number of Trees`= n_trees,`Number of Dead Trees`= n_dead,`Fraction Dead`= frac_dead) |>mutate(`Percent Dead`=percent(`Fraction Dead`, accuracy =0.1)) |>kable(format ="html",align =c("c", "r", "r", "r", "r"),caption ="Top 10 NYC Council Districts by Fraction of Dead Street Trees") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Top 10 NYC Council Districts by Fraction of Dead Street Trees
Council District
Number of Trees
Number of Dead Trees
Fraction Dead
Percent Dead
16
6518
361
0.0553851
5.5%
8
7306
306
0.0418834
4.2%
17
11851
482
0.0406717
4.1%
15
8044
321
0.0399055
4.0%
14
6240
224
0.0358974
3.6%
10
6501
226
0.0347639
3.5%
3
8631
283
0.0327888
3.3%
34
10812
349
0.0322789
3.2%
1
5694
180
0.0316122
3.2%
7
6572
207
0.0314973
3.1%
Answer (Q3): Council District 16 has the highest fraction of dead trees,
with about 5.5% of its street trees classified as dead.
Q4: What is the most common tree species in Manhattan?
Show code
library(sf)library(dplyr)library(knitr)library(kableExtra)library(scales)# Ensure trees_districts exists (same pattern as earlier questions) ----------if (!exists("trees_districts")) {trees_districts <-st_join(trees_sf,council_districts |>dplyr::select(CounDist, Shape_Area),join = st_within,left =TRUE) |>mutate(borough =case_when(CounDist >=1& CounDist <=10~"Manhattan",CounDist >=11& CounDist <=18~"Bronx",CounDist >=19& CounDist <=32~"Queens",CounDist >=33& CounDist <=48~"Brooklyn",CounDist >=49& CounDist <=51~"Staten Island",TRUE~NA_character_))}# Filter to Manhattan and tally species (spc_common) -------------------------manhattan_species <- trees_districts |>st_drop_geometry() |>filter(borough =="Manhattan") |>filter(!is.na(spc_common)) |>count(spc_common, sort =TRUE, name ="n_trees")# Top species for inline answerq4_top <- manhattan_species |>slice_head(n =1)# Pretty table: Top 10 most common species in Manhattan ----------------------manhattan_species |>slice_head(n =10) |>rename(`Common Species Name`= spc_common,`Number of Trees`= n_trees) |>mutate(`Number of Trees`=comma(`Number of Trees`)) |>kable(format ="html",align =c("l", "r"),caption ="Top 10 Most Common Street Tree Species in Manhattan") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Top 10 Most Common Street Tree Species in Manhattan
Common Species Name
Number of Trees
honeylocust
13,900
Callery pear
7,537
ginkgo
6,011
pin oak
4,893
Sophora
4,628
London planetree
4,497
Japanese zelkova
3,920
littleleaf linden
3,570
American elm
1,830
American linden
1,779
Answer (Q4): In Manhattan, the most common street tree species is honeylocust, with 13,900 recorded trees.
Q5: What is the species of the tree closest to Baruch’s campus?
Show code
library(sf)library(dplyr)library(knitr)library(kableExtra)library(scales)library(ggplot2)# 1. Approximate coordinates of Baruch College (23rd St & Lexington Ave) -----baruch_lat <-40.740173baruch_lon <--73.98337# Create an sf point for Baruch in WGS84baruch_point <-st_sfc(st_point(c(baruch_lon, baruch_lat)), # c(lon, lat)crs =4326)# 2. Make sure trees_sf has a CRS and matches Baruch's CRS -------------------if (is.na(st_crs(trees_sf))) {st_crs(trees_sf) <-4326}# 3. Compute geodesic distance from each tree to Baruch (in meters) ---------dist_vec <-st_distance(trees_sf, baruch_point)dist_m <-as.numeric(dist_vec) # drop units for easier use# 4. Find the nearest tree ---------------------------------------------------q5_nearest <- trees_sf |>mutate(distance_m = dist_m) |>slice_min(distance_m, n =1)# 5. Pretty table with key info ---------------------------------------------q5_nearest |>st_drop_geometry() |>transmute(`Common Species Name`= spc_common,`Latin Name`= spc_latin,`Tree Status`= status,`Distance (meters)`=round(distance_m, 1),`Distance (miles)`=round(distance_m /1609.34, 3)) |>kable(format ="html",align =c("l", "l", "c", "r", "r"),caption ="Nearest Street Tree to Baruch College (by straight-line distance)") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Nearest Street Tree to Baruch College (by straight-line distance)
Common Species Name
Latin Name
Tree Status
Distance (meters)
Distance (miles)
Callery pear
Pyrus calleryana
Alive
36.5
0.023
Show code
# 6. Find trees within ~200 meters of Baruch for the map --------------------near_idx <-st_is_within_distance(trees_sf,baruch_point,dist =200,sparse =FALSE# <-- return a logical matrix so [,1] works)trees_near_baruch <- trees_sf[near_idx[, 1], ]# 7. Build bounding box for zoomed-in map -----------------------------------bbox <-st_bbox(trees_near_baruch)# 8. Create an sf object for the Baruch point for plotting -------------------baruch_sf <-st_sf(label ="Baruch College",geometry = baruch_point)# 9. Plot: zoomed-in map around Baruch, nearest tree highlighted ------------ggplot() +# Optional: faint council district boundaries in the backgroundgeom_sf(data = council_districts,fill =NA,color ="grey85",linewidth =0.3) +# Trees near Baruchgeom_sf(data = trees_near_baruch,color ="darkgreen",alpha =0.5,size =0.8) +# Nearest tree: bright redgeom_sf(data = q5_nearest,color ="red",size =2) +# Baruch point: blue Xgeom_sf(data = baruch_sf,color ="blue",shape =4,size =3,stroke =1.1) +coord_sf(xlim =c(bbox["xmin"], bbox["xmax"]),ylim =c(bbox["ymin"], bbox["ymax"])) +labs(title ="Nearest Street Tree to Baruch College",subtitle ="Trees within ~200 meters of campus; nearest tree shown in red",caption ="Data: NYC Street Tree Census (NYC Open Data)") +theme_minimal()
Answer (Q5): The street tree closest to Baruch’s campus is a Callery pear (Latin name Pyrus calleryana),
located about 36.5 meters
(≈ 0.023 miles) from campus.
Government Project Design
Task 5: NYC Parks Proposal
Show code
# Task 5 setup for District 38 (Brooklyn)library(sf)library(dplyr)library(ggplot2)library(kableExtra)library(scales)# Focus district: where you livefocus_district <-38# Make sure trees_districts exists (reuse logic from Task 4 if needed)if (!exists("trees_districts")) {trees_districts <-st_join(trees_sf,council_districts |>dplyr::select(CounDist, Shape_Area),join = st_within,left =TRUE) |>mutate(borough =case_when(CounDist >=1& CounDist <=10~"Manhattan",CounDist >=11& CounDist <=18~"Bronx",CounDist >=19& CounDist <=32~"Queens",CounDist >=33& CounDist <=48~"Brooklyn",CounDist >=49& CounDist <=51~"Staten Island",TRUE~NA_character_))}# Trees and geometry for District 38 -----------------------------------------trees_focus <- trees_districts |>filter(CounDist == focus_district)focus_borough <- trees_focus$borough[!is.na(trees_focus$borough)][1]district_geom <- council_districts |>filter(CounDist == focus_district)# Summary of dead trees in District 38 ---------------------------------------dead_stats <- trees_focus |>st_drop_geometry() |>mutate(is_dead = status =="Dead") |>summarize(n_trees =n(),n_dead =sum(is_dead, na.rm =TRUE),frac_dead = n_dead / n_trees)# Dead-tree stats for ALL districts ------------------------------------------dead_fraction <- trees_districts |>st_drop_geometry() |>filter(!is.na(CounDist)) |>mutate(is_dead = status =="Dead") |>group_by(CounDist, borough) |>summarize(n_trees =n(),n_dead =sum(is_dead, na.rm =TRUE),frac_dead = n_dead / n_trees,.groups ="drop")# Row for District 38focus_row <- dead_fraction |>filter(CounDist == focus_district)# Top 3 other districts by dead fractionothers <- dead_fraction |>filter(CounDist != focus_district) |>arrange(desc(frac_dead)) |>slice_head(n =3)compare_districts <-bind_rows(focus_row, others)# Dead trees only in District 38 (for the map)dead_trees_focus <- trees_focus |>filter(status =="Dead")
Show code
# Map of dead trees in Council District 38# Bounding box from the district polygonbbox_38 <-st_bbox(district_geom)ggplot() +geom_sf(data = district_geom,fill ="grey95",color ="black",linewidth =0.6) +geom_sf(data = dead_trees_focus,color ="red",alpha =0.6,size =0.7) +coord_sf(xlim =c(bbox_38["xmin"], bbox_38["xmax"]),ylim =c(bbox_38["ymin"], bbox_38["ymax"])) +labs(title ="Dead Street Trees in NYC Council District 38",subtitle =paste0("District ", focus_district, " in ", focus_borough),caption ="Data: NYC Street Tree Census (NYC Open Data)") +theme_minimal()
Show code
# Bar chart: dead-tree fraction in District 38 vs 3 other districtscompare_plot_data <- compare_districts |>mutate(district_label =paste0("D", CounDist, " (", borough, ")"),is_focus = CounDist == focus_district)ggplot(compare_plot_data, aes(x =reorder(district_label, frac_dead),y = frac_dead,fill = is_focus)) +geom_col() +coord_flip() +scale_y_continuous(labels = scales::percent) +scale_fill_manual(values =c("FALSE"="grey70", "TRUE"="darkgreen"),guide ="none") +labs(title ="Fraction of Dead Street Trees by District",subtitle =paste0("Council District ", focus_district," compared to three districts with high dead-tree fractions"),x ="Council District (Borough)",y ="Fraction of Trees That Are Dead") +theme_minimal()
Show code
# Table: Dead-tree burden in District 38 vs comparison districtscompare_plot_data |>arrange(desc(is_focus), desc(frac_dead)) |>transmute(`Council District`= CounDist,Borough = borough,`Number of Trees`= n_trees,`Dead Trees`= n_dead,`Percent Dead`=percent(frac_dead, accuracy =0.1),`Focus District?`=if_else(is_focus, "Yes", "No")) |>kable(format ="html",align ="c",caption ="Dead Tree Burden in District 38 vs Three Comparison Districts") |>kable_styling(bootstrap_options =c("striped", "hover", "condensed"),full_width =FALSE,position ="center")
Dead Tree Burden in District 38 vs Three Comparison Districts
Council District
Borough
Number of Trees
Dead Trees
Percent Dead
Focus District?
38
Brooklyn
9566
260
2.7%
Yes
16
Bronx
6518
361
5.5%
No
8
Manhattan
7306
306
4.2%
No
17
Bronx
11851
482
4.1%
No
Proposed Project: Replacing Dead Street Trees in Council District 38 (Brooklyn)
This proposal recommends a targeted Dead Street Tree Removal and Replacement Program in New York City Council District 38, located in Brooklyn.
The aim of this initiative is to reduce public safety risks, restore tree canopy coverage, and enhance
streetscape quality in a district that combines dense residential blocks, commercial corridors,
and heavily used pedestrian routes.
Background and Motivation
According to the NYC Street Tree Census, Council District 38 currently contains approximately 9,566 recorded street trees. Of these, an estimated 260 trees—approximately 2.7% of the inventory—are classified as dead.
Dead street trees present several concerns:
Elevated risk of limb or trunk failure, particularly during storms and high-wind events;
Reduced shade and cooling, especially on blocks with substantial pedestrian activity;
Deterioration of the visual quality of residential streets and commercial frontages.
The spatial analysis of the tree census data indicates that dead trees in District 38
are not uniformly distributed. Instead, they cluster along specific corridors and blocks within the district.
This pattern supports a targeted intervention strategy that can be operationally efficient and highly visible to residents.
Quantitative Justification and Comparative Context
To place District 38 in context, its dead-tree burden is compared to that of three additional
council districts with relatively high fractions of dead trees. The accompanying bar chart and summary table show that:
District 38 has a dead-tree fraction of approximately 2.7%, placing it among the higher-burden districts citywide.
Many dead trees in District 38 are located on blocks with significant foot traffic,
commercial activity, and community facilities. As a result, the safety and quality-of-life impact of each dead tree
is comparatively greater than in lower-density areas.
This analysis supports prioritizing District 38 for a concentrated dead-tree remediation effort.
Proposed Scope of Work
The proposed program would include the following components:
Removal of dead street trees
Removal of approximately 260 dead street trees within Council District 38.
Prioritization of locations near schools, playgrounds, senior centers, transit stops, and major pedestrian corridors.
Replacement plantings
Replanting at or near removal sites using resilient, climate-adapted species that support long-term canopy goals.
Coordination with existing NYC Parks planting standards and species guidelines.
Community coordination and stewardship
Engagement with community boards, local schools, and neighborhood organizations to communicate planned removals and plantings.
Encouragement of local stewardship (e.g., watering and care for newly planted trees), where appropriate.
Anticipated Benefits
Implementation of the Dead Street Tree Removal and Replacement Program in Council District 38 is expected to:
Improve public safety by addressing structurally compromised trees in a district with high pedestrian volumes;
Restore and stabilize the urban forest by replacing dead trees with healthy, well-selected species;
Enhance neighborhood aesthetics and comfort, particularly along key residential and commercial corridors;
Promote equity in urban forestry investments by directing resources to a district with both a substantial proportion of dead trees and intensive daily use by residents and businesses.
Overall, this initiative would represent a focused, data-driven investment in the resilience, safety, and livability of Council District 38 and its communities in Brooklyn.
Extra Credit Opportunity #01 — Improved Tree Map Visualizations